package representation;

import java.util.Collection;
import java.util.List;

import environment.Environment;


/** usage: new Memory(size);
 * 		Memory.alloc(size);
 * 		Memory newmem = mem.ref(address);
 * @author kindone
 *
 */
public class Memory {
	static final long serialVersionUID = 0;	
	static final int MAXSIZE = 4000;
	private RawMemory rawMemory; /** usually mere reference */
	private int address = 0;
	private boolean forward = true;	
	
	private Object lock = new Object();

	
	public Memory(int size)
	{
		this.rawMemory = new RawMemory(size);
		this.address = 0;
		forward = true;
		
		this.rawMemory.ensureCapacity(size);
		
		for(int i = 0; i < size; i++)
			this.rawMemory.add(Integer.valueOf(0));
		
	}
	
	public Memory(RawMemory space, int address) 
	{		
		this.rawMemory = space;
		this.address = address;		
		this.forward = true; 
	}
			
	public Memory(RawMemory space, int address, boolean forward) 
	{		
		this.rawMemory = space;
		this.address = address;		
		this.forward = this.forward ^ (!forward); /** reverse **/ 
	}
	
	
	/** ref(0) ref(memsize) **/
	public Memory ref(int address) 
	{	
		return new Memory(this.rawMemory, address, this.forward);		
	}
	
	public Memory ref(int address, boolean forward) 
	{		
		return new Memory(this.rawMemory, address, forward);
	}	
	
	public int getValue()
	{	
		synchronized (lock) {
			int addr = 0<= address && address <= getSize()-1 ? address : (address % getSize());  			
			return ((Integer)this.rawMemory.get(addr >= 0 ? addr : addr + getSize())).intValue();
		}
		
	}
	
	public int getValue(int address)
	{
		address = this.address + address;
		synchronized (lock) {
			int addr = 0<= address && address <= getSize()-1 ? address : (address % getSize());  
			return ((Integer)this.rawMemory.get(addr >= 0 ? addr : addr + getSize())).intValue();
		}
	}
	
	public void setValue(int value)
	{
		synchronized (lock) {
			int addr = 0<= address && address <= getSize()-1 ? address : (address % getSize());
			this.rawMemory.set(addr >= 0 ? addr : addr + getSize(), Integer.valueOf(value));	
		}		
	}
	
	public void setValue(int position, int value)
	{
		address = this.address + address;
		synchronized (lock) {
			int addr = 0<= address && address <= getSize()-1 ? address : (address % getSize());			
			this.rawMemory.set(addr >=0 ? addr : addr + getSize(), Integer.valueOf(value));			
		}
	}

	public int getAddress()
	{
		return this.address;
	}

	public int getAddressUpperBound()
	{
		return getSize()-1;
	}
	
	private void setSize(int size)
	{
		synchronized (lock) {
			int oldSize = getSize();
			
			if(size<=0 || size == oldSize)
				return;
			
			if(size < oldSize)
			{
				this.rawMemory.subList(size, oldSize).clear();
//				for(int i = oldSize; i > size; i--)				
//					this.rawMemory.remove(i-1);
			}
			else if(size > oldSize)
			{
				this.rawMemory.ensureCapacity(size);
				
				for(int i = oldSize; i < size; i++)
					this.rawMemory.add(Integer.valueOf(0));			
			}	
		}
				
	}
	
	public int getSize()
	{		
		return this.rawMemory.size();
	}
	
	public int diminish(int sizeBy)
	{
		int oldSize = this.rawMemory.size();
		
		if(sizeBy<=0)
			return oldSize;
		
		if(oldSize - sizeBy > 0 )
		{
			setSize(oldSize - sizeBy);
			return oldSize - sizeBy;
		}
		else
		{
			setSize(1);
			return 1;
		}	
	}
	
	public int expand(int sizeBy)
	{
		int oldSize = this.rawMemory.size();
		if(sizeBy<=0)
			return oldSize;
				
		setSize(oldSize + sizeBy < MAXSIZE ? oldSize + sizeBy : MAXSIZE);
		
		return this.rawMemory.size();
	}
	
	public void lock()
	{
		rawMemory.locked = true;
	}
	
	public void unlock()
	{
		rawMemory.locked = false;
	}
	
	public boolean isLocked()
	{
		return rawMemory.locked;
	}
	
	/** need to resolve synchronization issue **/
	public void copyTo(Memory target, int amount)
	{	
		/**
		synchronized (lock) {
			amount = amount % this.rawMemory.size();
			amount = amount % target.rawMemory.size();
			
			for(int i = 0; i < amount; i++)
			{
				int i1 = this.forward ? i : -i;
				int i2 = target.forward ? i : -i;	
				if(!target.isLocked()) {
					synchronized(target.lock)
					{
						int index1 = (this.address + i1) % this.rawMemory.size();
						int index2 = (target.address + i2) % target.rawMemory.size();
						
						target.rawMemory.set(index2 >= 0 ? index2 : index2 + target.rawMemory.size(), this.rawMemory.get(index1 >= 0 ? index1 : index1 + this.rawMemory.size()) );
					}
				}
			}
		}	
		**/
		int thisstart = this.address;
		int targetstart = target.address; 

		synchronized(lock) {
			amount = amount % this.rawMemory.size();
			amount = amount % target.rawMemory.size();
		}

		for(int i = 0; i < amount ; i++)
		{
			int i1 = this.forward ? i : -i;
			int i2 = target.forward ? i : -i;
			target.setValue(i2, this.getValue(i1));
		}
	}
	
	public void mutate(double amount)
	{

		synchronized (lock) {
			if((int)(amount*rawMemory.size()) <= 0)
				return;

			int offset = Environment.Randomizer.integer((int)(amount*rawMemory.size()));
			int cap = rawMemory.size();
			int size = (int)(amount*cap);	
			
			for(int i = 0; i < size; i++) {
				int position = (int)(offset+Environment.Randomizer.gaussian()*size);
				setValue(position, Environment.Randomizer.integer());
			}
		}
		
	}

	public static Memory alloc(int size)
	{
		return new Memory(size);
	}	
	
}
